/****************************************************
 * 创建人：     @author tandun    
 * 创建时间: 2021年7月21日/下午1:46:09
 * 项目名称：  dfas-common-file_pinan
 * 文件名称: ZipUtils.java
 * 文件描述: @Description: TODO(用一句话描述该文件做什么)
 *
 * All rights Reserved, Designed By 投资交易团队
 * @Copyright:2016-2021
 *
 ********************************************************/
package com.springboot.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 包名称： com.win.dfas.file.util
 * 类名称：ZipUtils
 * 类描述：文件压缩工具
 * 创建人：@author tandun
 * 创建时间：2021年7月21日/下午1:46:37
 */
@Slf4j
public class ZipUtils {

    public static final int BUFFER_SIZE = 1024;

    public static final String zipFileSuffix = ".zip";

    /**
     * 压缩下载目录
     * @param dir
     */
    public static void downloadZipFiles(String dir) {
        // 获取路径下所有文件打成压缩包
        File dirFile = new File(dir);
        if (!dirFile.exists()) {
            throw new RuntimeException("");
        }
        List<ZipOutBO> list = new ArrayList<>();
        File[] files = dirFile.listFiles();
        InputStream inputStream = null;
        List<String> fileNameList = new ArrayList<>();
        for (File file : files) {
            if (file.isDirectory()) {
                continue;
            }
            ZipOutBO out = new ZipOutBO();
            out.setFileName(file.getName());
            fileNameList.add(file.getName());
            try {
                inputStream = new FileInputStream(file);
                byte[] bytes = IoUtil.readBytes(inputStream);
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                byteArrayOutputStream.write(bytes);
                out.setByteArrayOutputStream(byteArrayOutputStream);
                list.add(out);
            } catch (Exception e) {
                log.error("文件打成压缩包失败：" + e);
                e.printStackTrace();
            } finally {
                IoUtil.close(inputStream);
            }
        }
        if (CollUtil.isNotEmpty(list)) {
            log.info("开始压缩下载文件：{}", JSON.toJSONString(fileNameList));
            dowloadZipByFilePath(list);
        }

    }

    /**
     * @param list
     * @description: 压缩下载指定文件夹下的所有文件
     */
    public static void dowloadZipByFilePath(List<ZipOutBO> list) {
        try {
            List<ZipInputBO> zipFileBOS = new ArrayList<>();
            if (CollectionUtil.isNotEmpty(list)) {
                for (ZipOutBO zipOutBO : list) {
                    ByteArrayOutputStream byteArrayOutputStream = zipOutBO.getByteArrayOutputStream();
                    ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
                    //压入文件
                    ZipInputBO zipFileBO = new ZipInputBO();
                    zipFileBO.setFileName(zipOutBO.getFileName());
                    zipFileBO.setInputStream(byteArrayInputStream);
                    zipFileBOS.add(zipFileBO);
                    if (ObjectUtil.isNotEmpty(byteArrayInputStream)) {
                        byteArrayOutputStream.close();
                    }
                }
            }
            //下载压缩包
            if (CollectionUtil.isNotEmpty(zipFileBOS)) {
                ZipUtils.downloadZipFiles(zipFileBOS, getResponse(), getRequest());
            }
        } catch (Exception e) {
            log.error("下载压缩包指出错：", e);
        }
    }

    /**
     * @param response
     * @param fileMap
     * @param zipPath
     * @throws
     * @Title: zipFiles
     * @Description: TODO(这里用一句话描述这个方法的作用)
     * @return: void
     * @author: tandun
     * @Date: 2021年7月22日/下午5:44:17
     */
    public static void zipFiles(HttpServletResponse response, Map<String, File> fileMap, File zipPath) {
        //通过UUID随机生成一个zip文件名
        String uuid = UUID.randomUUID().toString();
        String fileName = uuid + ".zip";
        String zipName = zipPath + File.separator + fileName;
        File zipFile = new File(zipName);
        try {
            if (!zipFile.exists()) {
                zipFile.createNewFile();
            }
        } catch (Exception e) {
            log.info("创建文件目录异常：", e);
        }
        //压缩文件
        boolean flag = writeZip(fileMap, zipName);
        try {
            if (flag) {
                //下载文件
                download(response, fileName, zipFile);
            } else {
                throw new RuntimeException("");
            }
        } catch (Exception e) {
            log.info("压缩文件异常：{}", e.getMessage());
            throw new RuntimeException("");
        } finally {
            try {
                boolean deleteFile = FileUtil.del(zipName);
                if (deleteFile) {
                    log.info("删除临时压缩包成功：{}", JSON.toJSONString(zipName));
                } else {
                    log.info("删除临时压缩包失败：{}", JSON.toJSONString(zipName));
                }

            } catch (Exception e) {
                log.info("删除文件异常：", e);
            }
        }
    }

    /**
     * 压缩文件方法
     *
     * @param fileMap 压缩的文件的名字和路径
     * @param zipName 压缩包的文件路径
     * @return
     */
    public static boolean writeZip(Map<String, File> fileMap, String zipName) {
        boolean flag;
        OutputStream os = null;

        try {
            log.info("fileMap---------------{},",fileMap);
            log.info("压缩包的文件路径为 zipName is ：{}", zipName);
            os = new BufferedOutputStream(new FileOutputStream(zipName));
            ZipOutputStream zos = new ZipOutputStream(os);
            //设置文件名编码方式
            //zos.setEncoding("UTF-8");
            byte[] buf = new byte[8192];

            fileMap.forEach((zipPathName, files) -> {
                int len;
                File file = new File(files.getPath());
                if (file.isFile()) {
                    String fileName = StrUtil.builder(String.valueOf(zipPathName)).append(File.separator).append(files.getName()).toString();
                    log.info("flieName :", fileName);
                    ZipEntry ze = new ZipEntry(fileName);
                    //解决linux乱码问题----
                    //((Object) ze.setUnixMode(644);
                    //zos.putNextEntry(new ZipEntry("1/"));
                    try {
                        zos.putNextEntry(ze);
                        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
                        while ((len = bis.read(buf)) > 0) {
                            zos.write(buf, 0, len);
                        }
                        zos.closeEntry();
                    } catch (IOException e) {
                        log.error("异常处理：", e);
                    }

                }

            });


            zos.closeEntry();
            zos.close();
            flag = true;
        } catch (Exception e) {
            flag = false;
            log.error("writeZip error: ", e);
            log.error(e.getMessage());
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    log.error("异常处理：", e);
                }
            }

        }
        return flag;
    }

    //下载方法
    public static void download(HttpServletResponse response, String fileName, File file) {
        response.reset();
        response.setCharacterEncoding("utf-8");
        response.setContentType("multipart/form-data");

        response.setHeader("Content-Length", String.valueOf(file.length()));
        //用于记录以完成的下载的数据量，单位是byte
        InputStream inputStream = null;
        OutputStream os = null;
        try {
            response.setHeader("Content-Disposition", String.format("attachment; filename=\"%s\"", URLEncoder.encode(fileName, "UTF-8")));
            //打开本地文件流
            inputStream = new FileInputStream(file);
            //激活下载操作
            os = response.getOutputStream();
            //循环写入输出流
            byte[] b = new byte[1024 * 1024];
            int length;
            while ((length = inputStream.read(b)) > 0) {
                os.write(b, 0, length);
            }
        } catch (IOException e) {
            log.error("异常处理：", e);
        } finally {
            try {
                if (os != null) {
                    os.close();
                }
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
                log.error("异常处理：", e);
            }
        }
    }

    /**
     * 递归删除目录下的所有文件及子目录下所有文件
     *
     * @param dir 将要删除的文件目录
     * @return boolean Returns "true" if all deletions were successful.
     * If a deletion fails, the method stops attempting to
     * delete and returns "false".
     */
    private static boolean deleteDir(File dir) {
        if (dir.isDirectory()) {
            String[] children = dir.list();
            for (int i = 0; i < children.length; i++) {
                boolean success = deleteDir(new File(dir, children[i]));
                if (!success) {
                    return false;
                }
            }
        }
        // 目录此时为空，可以删除
        return dir.delete();
    }

    /**
     * 下载文件
     *
     * @param request
     * @param response
     * @return
     */
    public static void downloadZipFiles(List<ZipInputBO> zipFileBOS, HttpServletResponse response, HttpServletRequest request) {
        String fileName = System.currentTimeMillis() + zipFileSuffix;
        ServletOutputStream out = null;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ZipOutputStream zos = new ZipOutputStream(bos);
        try {
            response.reset();
            byte[] bs = null;
            List<byte[]> bytelist = new ArrayList<byte[]>();
            if (CollectionUtil.isNotEmpty(zipFileBOS)) {
                for (ZipInputBO zipFileBO : zipFileBOS) {
                    //压缩文件
                    byte[] bytes = IoUtil.readBytes(zipFileBO.getInputStream());
                    zipFileBO.getInputStream().close();
                    ZipEntry entry = new ZipEntry(zipFileBO.getFileName());
                    zos.putNextEntry(entry);
                    zos.write(bytes);
                }
                zos.closeEntry();
                zos.close();
                bs = bos.toByteArray();
                bos.close();
                bytelist.add(bs);
                bs = bytesCombine(bytelist);
                setHeader(request, response, fileName);
                out = response.getOutputStream();
                out.write(bs);
                out.flush();
            }
        } catch (IOException e) {
            log.error("[文件下载]：", e);
        } finally {
            try {
                if (null != out) {
                    out.close();
                }
            } catch (IOException e) {
                log.error("[文件下载]：", e);
            }
        }
    }

    /**
     * 解压 zip 文件
     *
     * @param zipFile zip 压缩文件
     * @param destDir zip 压缩文件解压后保存的目录
     * @return 返回 zip 压缩文件里的文件名的 list
     * @throws Exception
     */
    public static List<String> unZip(File zipFile, String destDir) throws Exception {
        // 如果 destDir 为 null, 空字符串, 或者全是空格, 则解压到压缩文件所在目录
        if (StringUtils.isBlank(destDir)) {
            destDir = zipFile.getParent();
        }

        destDir = destDir.endsWith(File.separator) ? destDir : destDir + File.separator;
        ZipArchiveInputStream is = null;
        List<String> fileNames = new ArrayList<String>();

        try {
            is = new ZipArchiveInputStream(new BufferedInputStream(new FileInputStream(zipFile), BUFFER_SIZE));
            ZipArchiveEntry entry = null;

            while ((entry = is.getNextZipEntry()) != null) {
                fileNames.add(entry.getName());

                if (entry.isDirectory()) {
                    File directory = new File(destDir, entry.getName());
                    directory.mkdirs();
                } else {
                    OutputStream os = null;
                    try {
                        os = new BufferedOutputStream(new FileOutputStream(new File(destDir, entry.getName())), BUFFER_SIZE);
                        IOUtils.copy(is, os);
                    } finally {
                        IOUtils.closeQuietly(os);
                    }
                }
            }
        } catch (Exception e) {
            log.error("异常处理：", e);
            throw e;
        } finally {
            IOUtils.closeQuietly(is);
        }

        return fileNames;
    }

    /**
     * byte数组的合并
     *
     * @param srcArrays
     * @return
     */
    private static byte[] bytesCombine(List<byte[]> srcArrays) {
        byte[] destArrays = null;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try {
            for (byte[] srcArray : srcArrays) {
                bos.write(srcArray);
            }
            destArrays = bos.toByteArray();
            bos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                bos.close();
            } catch (IOException e) {
            }
        }
        return destArrays;
    }

    /**
     * @param request
     * @param response
     * @param fileName
     * @Title: setHeader
     * @Description: 不同浏览器设置header
     */
    public static void setHeader(HttpServletRequest request, HttpServletResponse response, String fileName) {
        try {
            response.reset();
            response.setContentType("application/octet-stream");
            response.setHeader(HttpHeaders.CONTENT_TYPE, "application/octet-stream");
            String browser = request.getHeader(HttpHeaders.USER_AGENT);
            if (-1 < browser.indexOf("MSIE 6.0") || -1 < browser.indexOf("MSIE 7.0")) {
                // IE6, IE7 浏览器
                response.addHeader(HttpHeaders.CONTENT_DISPOSITION,
                        "attachment;filename=" + new String(fileName.getBytes("UTF-8"), "ISO8859-1"));
            } else if (-1 < browser.indexOf("MSIE 8.0")) {
                // IE8
                response.addHeader(HttpHeaders.CONTENT_DISPOSITION,
                        "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
            } else if (-1 < browser.indexOf("MSIE 9.0")) {
                // IE9
                response.addHeader(HttpHeaders.CONTENT_DISPOSITION,
                        "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
            } else if (-1 < browser.indexOf("Chrome")) {
                // 谷歌
                response.addHeader(HttpHeaders.CONTENT_DISPOSITION,
                        "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
            } else if (-1 < browser.indexOf("Safari")) {
                // 苹果
                response.addHeader(HttpHeaders.CONTENT_DISPOSITION,
                        "attachment;filename=" + new String(fileName.getBytes("UTF-8"), "ISO8859-1"));
            } else {
                // 火狐或者其他的浏览器
                response.addHeader(HttpHeaders.CONTENT_DISPOSITION,
                        "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("发送返回下载文件出错：", e);
        }
    }

    public static void zipDirectory(File directory, String parentPath, ZipOutputStream zipOut) throws IOException {
        File[] files = directory.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                zipDirectory(file, parentPath + File.separatorChar + file.getName(), zipOut);
                continue;
            }
            try (FileInputStream fis = new FileInputStream(file)) {
                ZipEntry zipEntry = new ZipEntry(parentPath + File.separatorChar + file.getName());
                zipOut.putNextEntry(zipEntry);
                IoUtil.copy(fis, zipOut);
            }
        }
    }

    public static void downloadDirByZip(String directory, HttpServletResponse response) throws IOException {
        File dir = new File(directory);
        if (dir.exists() && dir.isDirectory()) {
            response.setContentType("application/zip");
            response.setHeader("Content-Disposition", "attachment; filename=\"files.zip\"");
            try (ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream())) {
                zipDirectory(dir, dir.getName(), zipOut);
            }
        }
    }

    /**
     * 获取请求信息
     *
     * @return
     * @throws
     * @Title: getRequest
     * @return: HttpServletRequest
     * @author: hechengcheng
     * @Date: 2019年9月17日/下午7:55:18
     */
    public static HttpServletRequest getRequest() {

        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        if (ObjectUtil.isEmpty(requestAttributes)) {
            return null;
        }

        return ((ServletRequestAttributes) requestAttributes).getRequest();
    }

    /**
     *
     * 获取返回信息
     * @Title: getResponse
     * @return
     * @return: HttpServletResponse
     * @throws
     * @author: hechengcheng
     * @Date:  2019年9月17日/下午7:55:18
     */
    public static HttpServletResponse getResponse() {

        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        if (ObjectUtil.isEmpty(requestAttributes)) {
            return null;
        }

        return ((ServletRequestAttributes) requestAttributes).getResponse();
    }

}


